1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 package com.google.common.collect.testing;
18
19 import static junit.framework.Assert.assertEquals;
20 import static junit.framework.Assert.fail;
21
22 import com.google.common.annotations.GwtCompatible;
23
24 import junit.framework.AssertionFailedError;
25
26 import java.util.ArrayList;
27 import java.util.Arrays;
28 import java.util.Collection;
29 import java.util.Collections;
30 import java.util.HashSet;
31 import java.util.Iterator;
32 import java.util.List;
33 import java.util.ListIterator;
34 import java.util.NoSuchElementException;
35 import java.util.Set;
36 import java.util.Stack;
37
38
39
40
41
42
43
44
45
46
47
48 @GwtCompatible
49 abstract class AbstractIteratorTester<E, I extends Iterator<E>> {
50 private boolean whenNextThrowsExceptionStopTestingCallsToRemove;
51 private boolean whenAddThrowsExceptionStopTesting;
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73 public void ignoreSunJavaBug6529795() {
74 whenNextThrowsExceptionStopTestingCallsToRemove = true;
75 }
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94 public void stopTestingWhenAddThrowsException() {
95 whenAddThrowsExceptionStopTesting = true;
96 }
97
98 private Stimulus<E, ? super I>[] stimuli;
99 private final Iterator<E> elementsToInsert;
100 private final Set<IteratorFeature> features;
101 private final List<E> expectedElements;
102 private final int startIndex;
103 private final KnownOrder knownOrder;
104
105
106
107
108
109
110
111 private static final class PermittedMetaException extends RuntimeException {
112 final Set<? extends Class<? extends RuntimeException>> exceptionClasses;
113
114 PermittedMetaException(
115 Set<? extends Class<? extends RuntimeException>> exceptionClasses) {
116 super("one of " + exceptionClasses);
117 this.exceptionClasses = exceptionClasses;
118 }
119
120 PermittedMetaException(Class<? extends RuntimeException> exceptionClass) {
121 this(Collections.singleton(exceptionClass));
122 }
123
124
125 boolean isPermitted(RuntimeException exception) {
126 for (Class<? extends RuntimeException> clazz : exceptionClasses) {
127 if (Platform.checkIsInstance(clazz, exception)) {
128 return true;
129 }
130 }
131 return false;
132 }
133
134
135 void assertPermitted(RuntimeException exception) {
136 if (!isPermitted(exception)) {
137
138 String message = "Exception " + exception.getClass()
139 + " was thrown; expected " + this;
140 Helpers.fail(exception, message);
141 }
142 }
143
144 @Override public String toString() {
145 return getMessage();
146 }
147
148 private static final long serialVersionUID = 0;
149 }
150
151 private static final class UnknownElementException extends RuntimeException {
152 private UnknownElementException(Collection<?> expected, Object actual) {
153 super("Returned value '"
154 + actual + "' not found. Remaining elements: " + expected);
155 }
156
157 private static final long serialVersionUID = 0;
158 }
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177 protected final class MultiExceptionListIterator implements ListIterator<E> {
178
179
180
181
182
183
184
185
186 final Stack<E> nextElements = new Stack<E>();
187
188
189
190
191 final Stack<E> previousElements = new Stack<E>();
192
193
194
195
196
197
198
199
200 Stack<E> stackWithLastReturnedElementAtTop = null;
201
202 MultiExceptionListIterator(List<E> expectedElements) {
203 Helpers.addAll(nextElements, Helpers.reverse(expectedElements));
204 for (int i = 0; i < startIndex; i++) {
205 previousElements.push(nextElements.pop());
206 }
207 }
208
209 @Override
210 public void add(E e) {
211 if (!features.contains(IteratorFeature.SUPPORTS_ADD)) {
212 throw new PermittedMetaException(UnsupportedOperationException.class);
213 }
214
215 previousElements.push(e);
216 stackWithLastReturnedElementAtTop = null;
217 }
218
219 @Override
220 public boolean hasNext() {
221 return !nextElements.isEmpty();
222 }
223
224 @Override
225 public boolean hasPrevious() {
226 return !previousElements.isEmpty();
227 }
228
229 @Override
230 public E next() {
231 return transferElement(nextElements, previousElements);
232 }
233
234 @Override
235 public int nextIndex() {
236 return previousElements.size();
237 }
238
239 @Override
240 public E previous() {
241 return transferElement(previousElements, nextElements);
242 }
243
244 @Override
245 public int previousIndex() {
246 return nextIndex() - 1;
247 }
248
249 @Override
250 public void remove() {
251 throwIfInvalid(IteratorFeature.SUPPORTS_REMOVE);
252
253 stackWithLastReturnedElementAtTop.pop();
254 stackWithLastReturnedElementAtTop = null;
255 }
256
257 @Override
258 public void set(E e) {
259 throwIfInvalid(IteratorFeature.SUPPORTS_SET);
260
261 stackWithLastReturnedElementAtTop.pop();
262 stackWithLastReturnedElementAtTop.push(e);
263 }
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278 void promoteToNext(E e) {
279 if (nextElements.remove(e)) {
280 nextElements.push(e);
281 } else {
282 throw new UnknownElementException(nextElements, e);
283 }
284 }
285
286 private E transferElement(Stack<E> source, Stack<E> destination) {
287 if (source.isEmpty()) {
288 throw new PermittedMetaException(NoSuchElementException.class);
289 }
290
291 destination.push(source.pop());
292 stackWithLastReturnedElementAtTop = destination;
293 return destination.peek();
294 }
295
296 private void throwIfInvalid(IteratorFeature methodFeature) {
297 Set<Class<? extends RuntimeException>> exceptions
298 = new HashSet<Class<? extends RuntimeException>>();
299
300 if (!features.contains(methodFeature)) {
301 exceptions.add(UnsupportedOperationException.class);
302 }
303
304 if (stackWithLastReturnedElementAtTop == null) {
305 exceptions.add(IllegalStateException.class);
306 }
307
308 if (!exceptions.isEmpty()) {
309 throw new PermittedMetaException(exceptions);
310 }
311 }
312
313 private List<E> getElements() {
314 List<E> elements = new ArrayList<E>();
315 Helpers.addAll(elements, previousElements);
316 Helpers.addAll(elements, Helpers.reverse(nextElements));
317 return elements;
318 }
319 }
320
321 public enum KnownOrder { KNOWN_ORDER, UNKNOWN_ORDER }
322
323 @SuppressWarnings("unchecked")
324 AbstractIteratorTester(int steps, Iterable<E> elementsToInsertIterable,
325 Iterable<? extends IteratorFeature> features,
326 Iterable<E> expectedElements, KnownOrder knownOrder, int startIndex) {
327
328
329 stimuli = new Stimulus[steps];
330 if (!elementsToInsertIterable.iterator().hasNext()) {
331 throw new IllegalArgumentException();
332 }
333 elementsToInsert = Helpers.cycle(elementsToInsertIterable);
334 this.features = Helpers.copyToSet(features);
335 this.expectedElements = Helpers.copyToList(expectedElements);
336 this.knownOrder = knownOrder;
337 this.startIndex = startIndex;
338 }
339
340
341
342
343
344 protected abstract Iterable<? extends Stimulus<E, ? super I>>
345 getStimulusValues();
346
347
348
349
350
351
352
353
354 protected abstract I newTargetIterator();
355
356
357
358
359
360
361
362
363
364
365 protected void verify(List<E> elements) {}
366
367
368
369
370 public final void test() {
371 try {
372 recurse(0);
373 } catch (RuntimeException e) {
374 throw new RuntimeException(Arrays.toString(stimuli), e);
375 }
376 }
377
378 private void recurse(int level) {
379
380
381 if (level == stimuli.length) {
382
383 compareResultsForThisListOfStimuli();
384 } else {
385
386 for (Stimulus<E, ? super I> stimulus : getStimulusValues()) {
387 stimuli[level] = stimulus;
388 recurse(level + 1);
389 }
390 }
391 }
392
393 private void compareResultsForThisListOfStimuli() {
394 MultiExceptionListIterator reference =
395 new MultiExceptionListIterator(expectedElements);
396 I target = newTargetIterator();
397 boolean shouldStopTestingCallsToRemove = false;
398 for (int i = 0; i < stimuli.length; i++) {
399 Stimulus<E, ? super I> stimulus = stimuli[i];
400 if (stimulus.equals(remove) && shouldStopTestingCallsToRemove) {
401 break;
402 }
403 try {
404 boolean threwException = stimulus.executeAndCompare(reference, target);
405 if (threwException
406 && stimulus.equals(next)
407 && whenNextThrowsExceptionStopTestingCallsToRemove) {
408 shouldStopTestingCallsToRemove = true;
409 }
410 if (threwException
411 && stimulus.equals(add)
412 && whenAddThrowsExceptionStopTesting) {
413 break;
414 }
415 List<E> elements = reference.getElements();
416 verify(elements);
417 } catch (AssertionFailedError cause) {
418 Helpers.fail(cause,
419 "failed with stimuli " + subListCopy(stimuli, i + 1));
420 }
421 }
422 }
423
424 private static List<Object> subListCopy(Object[] source, int size) {
425 final Object[] copy = new Object[size];
426 System.arraycopy(source, 0, copy, 0, size);
427 return Arrays.asList(copy);
428 }
429
430 private interface IteratorOperation {
431 Object execute(Iterator<?> iterator);
432 }
433
434
435
436
437
438
439
440
441
442 private <T extends Iterator<E>> boolean internalExecuteAndCompare(
443 T reference, T target, IteratorOperation method)
444 throws AssertionFailedError {
445
446 Object referenceReturnValue = null;
447 PermittedMetaException referenceException = null;
448 Object targetReturnValue = null;
449 RuntimeException targetException = null;
450
451 try {
452 targetReturnValue = method.execute(target);
453 } catch (RuntimeException e) {
454 targetException = e;
455 }
456
457 try {
458 if (method == NEXT_METHOD && targetException == null
459 && knownOrder == KnownOrder.UNKNOWN_ORDER) {
460
461
462
463
464 @SuppressWarnings("unchecked")
465 E targetReturnValueFromNext = (E) targetReturnValue;
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481 MultiExceptionListIterator multiExceptionListIterator =
482 (MultiExceptionListIterator) reference;
483 multiExceptionListIterator.promoteToNext(targetReturnValueFromNext);
484 }
485
486 referenceReturnValue = method.execute(reference);
487 } catch (PermittedMetaException e) {
488 referenceException = e;
489 } catch (UnknownElementException e) {
490 Helpers.fail(e, e.getMessage());
491 }
492
493 if (referenceException == null) {
494 if (targetException != null) {
495 Helpers.fail(targetException,
496 "Target threw exception when reference did not");
497 }
498
499
500
501
502
503 assertEquals(referenceReturnValue, targetReturnValue);
504
505 return false;
506 }
507
508 if (targetException == null) {
509 fail("Target failed to throw " + referenceException);
510 }
511
512
513
514
515
516 referenceException.assertPermitted(targetException);
517
518 return true;
519 }
520
521 private static final IteratorOperation REMOVE_METHOD =
522 new IteratorOperation() {
523 @Override
524 public Object execute(Iterator<?> iterator) {
525 iterator.remove();
526 return null;
527 }
528 };
529
530 private static final IteratorOperation NEXT_METHOD =
531 new IteratorOperation() {
532 @Override
533 public Object execute(Iterator<?> iterator) {
534 return iterator.next();
535 }
536 };
537
538 private static final IteratorOperation PREVIOUS_METHOD =
539 new IteratorOperation() {
540 @Override
541 public Object execute(Iterator<?> iterator) {
542 return ((ListIterator<?>) iterator).previous();
543 }
544 };
545
546 private final IteratorOperation newAddMethod() {
547 final Object toInsert = elementsToInsert.next();
548 return new IteratorOperation() {
549 @Override
550 public Object execute(Iterator<?> iterator) {
551 @SuppressWarnings("unchecked")
552 ListIterator<Object> rawIterator = (ListIterator<Object>) iterator;
553 rawIterator.add(toInsert);
554 return null;
555 }
556 };
557 }
558
559 private final IteratorOperation newSetMethod() {
560 final E toInsert = elementsToInsert.next();
561 return new IteratorOperation() {
562 @Override
563 public Object execute(Iterator<?> iterator) {
564 @SuppressWarnings("unchecked")
565 ListIterator<E> li = (ListIterator<E>) iterator;
566 li.set(toInsert);
567 return null;
568 }
569 };
570 }
571
572 abstract static class Stimulus<E, T extends Iterator<E>> {
573 private final String toString;
574
575 protected Stimulus(String toString) {
576 this.toString = toString;
577 }
578
579
580
581
582
583
584
585 abstract boolean executeAndCompare(ListIterator<E> reference, T target);
586
587 @Override public String toString() {
588 return toString;
589 }
590 }
591
592 Stimulus<E, Iterator<E>> hasNext = new Stimulus<E, Iterator<E>>("hasNext") {
593 @Override boolean
594 executeAndCompare(ListIterator<E> reference, Iterator<E> target) {
595
596 assertEquals(reference.hasNext(), target.hasNext());
597 return false;
598 }
599 };
600 Stimulus<E, Iterator<E>> next = new Stimulus<E, Iterator<E>>("next") {
601 @Override boolean
602 executeAndCompare(ListIterator<E> reference, Iterator<E> target) {
603 return internalExecuteAndCompare(reference, target, NEXT_METHOD);
604 }
605 };
606 Stimulus<E, Iterator<E>> remove = new Stimulus<E, Iterator<E>>("remove") {
607 @Override boolean
608 executeAndCompare(ListIterator<E> reference, Iterator<E> target) {
609 return internalExecuteAndCompare(reference, target, REMOVE_METHOD);
610 }
611 };
612
613 @SuppressWarnings("unchecked")
614 List<Stimulus<E, Iterator<E>>> iteratorStimuli() {
615 return Arrays.asList(hasNext, next, remove);
616 }
617
618 Stimulus<E, ListIterator<E>> hasPrevious =
619 new Stimulus<E, ListIterator<E>>("hasPrevious") {
620 @Override boolean executeAndCompare(
621 ListIterator<E> reference, ListIterator<E> target) {
622
623 assertEquals(reference.hasPrevious(), target.hasPrevious());
624 return false;
625 }
626 };
627 Stimulus<E, ListIterator<E>> nextIndex =
628 new Stimulus<E, ListIterator<E>>("nextIndex") {
629 @Override boolean executeAndCompare(
630 ListIterator<E> reference, ListIterator<E> target) {
631 assertEquals(reference.nextIndex(), target.nextIndex());
632 return false;
633 }
634 };
635 Stimulus<E, ListIterator<E>> previousIndex =
636 new Stimulus<E, ListIterator<E>>("previousIndex") {
637 @Override boolean executeAndCompare(
638 ListIterator<E> reference, ListIterator<E> target) {
639 assertEquals(reference.previousIndex(), target.previousIndex());
640 return false;
641 }
642 };
643 Stimulus<E, ListIterator<E>> previous =
644 new Stimulus<E, ListIterator<E>>("previous") {
645 @Override boolean executeAndCompare(
646 ListIterator<E> reference, ListIterator<E> target) {
647 return internalExecuteAndCompare(reference, target, PREVIOUS_METHOD);
648 }
649 };
650 Stimulus<E, ListIterator<E>> add = new Stimulus<E, ListIterator<E>>("add") {
651 @Override boolean executeAndCompare(
652 ListIterator<E> reference, ListIterator<E> target) {
653 return internalExecuteAndCompare(reference, target, newAddMethod());
654 }
655 };
656 Stimulus<E, ListIterator<E>> set = new Stimulus<E, ListIterator<E>>("set") {
657 @Override boolean executeAndCompare(
658 ListIterator<E> reference, ListIterator<E> target) {
659 return internalExecuteAndCompare(reference, target, newSetMethod());
660 }
661 };
662
663 @SuppressWarnings("unchecked")
664 List<Stimulus<E, ListIterator<E>>> listIteratorStimuli() {
665 return Arrays.asList(
666 hasPrevious, nextIndex, previousIndex, previous, add, set);
667 }
668 }